!--------------------------------------------------------------------------------------------------!
!   CP2K: A general program to perform molecular dynamics simulations                              !
!   Copyright 2000-2025 CP2K developers group <https://cp2k.org>                                   !
!                                                                                                  !
!   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
!--------------------------------------------------------------------------------------------------!

! **************************************************************************************************
!> \brief Routines for a linear scaling quickstep SCF run based on the density
!>        matrix
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
MODULE dm_ls_scf
   USE arnoldi_api,                     ONLY: arnoldi_extremal
   USE bibliography,                    ONLY: Kolafa2004,&
                                              Kuhne2007,&
                                              cite_reference
   USE cp_control_types,                ONLY: dft_control_type
   USE cp_dbcsr_api,                    ONLY: &
        dbcsr_add, dbcsr_binary_read, dbcsr_binary_write, dbcsr_copy, dbcsr_create, &
        dbcsr_distribution_type, dbcsr_filter, dbcsr_get_info, dbcsr_get_occupation, &
        dbcsr_multiply, dbcsr_p_type, dbcsr_release, dbcsr_scale, dbcsr_set, dbcsr_type, &
        dbcsr_type_no_symmetry
   USE cp_dbcsr_contrib,                ONLY: dbcsr_checksum
   USE cp_external_control,             ONLY: external_control
   USE cp_log_handling,                 ONLY: cp_get_default_logger,&
                                              cp_logger_get_default_unit_nr,&
                                              cp_logger_type
   USE dm_ls_chebyshev,                 ONLY: compute_chebyshev
   USE dm_ls_scf_create,                ONLY: ls_scf_create
   USE dm_ls_scf_curvy,                 ONLY: deallocate_curvy_data,&
                                              dm_ls_curvy_optimization
   USE dm_ls_scf_methods,               ONLY: apply_matrix_preconditioner,&
                                              compute_homo_lumo,&
                                              density_matrix_sign,&
                                              density_matrix_sign_fixed_mu,&
                                              density_matrix_tc2,&
                                              density_matrix_trs4,&
                                              ls_scf_init_matrix_S
   USE dm_ls_scf_qs,                    ONLY: &
        ls_nonscf_energy, ls_nonscf_ks, ls_scf_dm_to_ks, ls_scf_init_qs, ls_scf_qs_atomic_guess, &
        matrix_ls_create, matrix_ls_to_qs, matrix_qs_to_ls, rho_mixing_ls_init
   USE dm_ls_scf_types,                 ONLY: ls_scf_env_type
   USE ec_env_types,                    ONLY: energy_correction_type
   USE input_constants,                 ONLY: ls_cluster_atomic,&
                                              ls_scf_pexsi,&
                                              ls_scf_sign,&
                                              ls_scf_tc2,&
                                              ls_scf_trs4,&
                                              transport_transmission
   USE input_section_types,             ONLY: section_vals_type
   USE iterate_matrix,                  ONLY: purify_mcweeny
   USE kinds,                           ONLY: default_path_length,&
                                              default_string_length,&
                                              dp
   USE machine,                         ONLY: m_flush,&
                                              m_walltime
   USE mathlib,                         ONLY: binomial
   USE molecule_types,                  ONLY: molecule_type
   USE pao_main,                        ONLY: pao_optimization_end,&
                                              pao_optimization_start,&
                                              pao_post_scf,&
                                              pao_update
   USE pexsi_methods,                   ONLY: density_matrix_pexsi,&
                                              pexsi_finalize_scf,&
                                              pexsi_init_scf,&
                                              pexsi_set_convergence_tolerance,&
                                              pexsi_to_qs
   USE qs_diis,                         ONLY: qs_diis_b_clear_sparse,&
                                              qs_diis_b_create_sparse,&
                                              qs_diis_b_step_4lscf
   USE qs_diis_types,                   ONLY: qs_diis_b_release_sparse,&
                                              qs_diis_buffer_type_sparse
   USE qs_environment_types,            ONLY: get_qs_env,&
                                              qs_environment_type
   USE qs_ks_types,                     ONLY: qs_ks_env_type
   USE qs_nonscf_utils,                 ONLY: qs_nonscf_print_summary
   USE qs_scf_post_gpw,                 ONLY: qs_scf_post_moments,&
                                              write_mo_free_results
   USE qs_scf_post_tb,                  ONLY: scf_post_calculation_tb
   USE transport,                       ONLY: external_scf_method,&
                                              transport_initialize
   USE transport_env_types,             ONLY: transport_env_type
#include "./base/base_uses.f90"

   IMPLICIT NONE

   PRIVATE

   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'dm_ls_scf'

   PUBLIC :: calculate_w_matrix_ls, ls_scf, post_scf_sparsities

CONTAINS

! **************************************************************************************************
!> \brief perform an linear scaling scf procedure: entry point
!>
!> \param qs_env ...
!> \param nonscf ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE ls_scf(qs_env, nonscf)
      TYPE(qs_environment_type), POINTER                 :: qs_env
      LOGICAL, INTENT(IN), OPTIONAL                      :: nonscf

      CHARACTER(len=*), PARAMETER                        :: routineN = 'ls_scf'

      INTEGER                                            :: handle
      LOGICAL                                            :: do_scf, pao_is_done
      TYPE(ls_scf_env_type), POINTER                     :: ls_scf_env

      CALL timeset(routineN, handle)
      do_scf = .TRUE.
      IF (PRESENT(nonscf)) do_scf = .NOT. nonscf

      ! Moved here from qs_environment to remove dependencies
      CALL ls_scf_create(qs_env)
      CALL get_qs_env(qs_env, ls_scf_env=ls_scf_env)

      IF (do_scf) THEN
         CALL pao_optimization_start(qs_env, ls_scf_env)
         pao_is_done = .FALSE.
         DO WHILE (.NOT. pao_is_done)
            CALL ls_scf_init_scf(qs_env, ls_scf_env, .FALSE.)
            CALL pao_update(qs_env, ls_scf_env, pao_is_done)
            CALL ls_scf_main(qs_env, ls_scf_env, .FALSE.)
            CALL pao_post_scf(qs_env, ls_scf_env, pao_is_done)
            CALL ls_scf_post(qs_env, ls_scf_env)
         END DO
         CALL pao_optimization_end(ls_scf_env)
      ELSE
         CALL ls_scf_init_scf(qs_env, ls_scf_env, .TRUE.)
         CALL ls_scf_main(qs_env, ls_scf_env, .TRUE.)
         CALL ls_scf_post(qs_env, ls_scf_env)
      END IF

      CALL timestop(handle)

   END SUBROUTINE ls_scf

! **************************************************************************************************
!> \brief initialization needed for scf
!> \param qs_env ...
!> \param ls_scf_env ...
!> \param nonscf ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE ls_scf_init_scf(qs_env, ls_scf_env, nonscf)
      TYPE(qs_environment_type), POINTER                 :: qs_env
      TYPE(ls_scf_env_type)                              :: ls_scf_env
      LOGICAL, INTENT(IN)                                :: nonscf

      CHARACTER(len=*), PARAMETER                        :: routineN = 'ls_scf_init_scf'

      INTEGER                                            :: handle, ispin, nspin, unit_nr
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: matrix_s, matrix_w
      TYPE(dft_control_type), POINTER                    :: dft_control
      TYPE(molecule_type), DIMENSION(:), POINTER         :: molecule_set
      TYPE(qs_ks_env_type), POINTER                      :: ks_env
      TYPE(section_vals_type), POINTER                   :: input

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      ! get basic quantities from the qs_env
      CALL get_qs_env(qs_env, nelectron_total=ls_scf_env%nelectron_total, &
                      matrix_s=matrix_s, &
                      matrix_w=matrix_w, &
                      ks_env=ks_env, &
                      dft_control=dft_control, &
                      molecule_set=molecule_set, &
                      input=input, &
                      has_unit_metric=ls_scf_env%has_unit_metric, &
                      para_env=ls_scf_env%para_env, &
                      nelectron_spin=ls_scf_env%nelectron_spin)

      ! needs forces ? There might be a better way to flag this
      ls_scf_env%calculate_forces = ASSOCIATED(matrix_w)

      ! some basic initialization of the QS side of things
      CALL ls_scf_init_qs(qs_env)

      ! create the matrix template for use in the ls procedures
      CALL matrix_ls_create(matrix_ls=ls_scf_env%matrix_s, matrix_qs=matrix_s(1)%matrix, &
                            ls_mstruct=ls_scf_env%ls_mstruct)

      nspin = ls_scf_env%nspins
      IF (ALLOCATED(ls_scf_env%matrix_p)) THEN
         DO ispin = 1, SIZE(ls_scf_env%matrix_p)
            CALL dbcsr_release(ls_scf_env%matrix_p(ispin))
         END DO
      ELSE
         ALLOCATE (ls_scf_env%matrix_p(nspin))
      END IF

      DO ispin = 1, nspin
         CALL dbcsr_create(ls_scf_env%matrix_p(ispin), template=ls_scf_env%matrix_s, &
                           matrix_type=dbcsr_type_no_symmetry)
      END DO

      ALLOCATE (ls_scf_env%matrix_ks(nspin))
      DO ispin = 1, nspin
         CALL dbcsr_create(ls_scf_env%matrix_ks(ispin), template=ls_scf_env%matrix_s, &
                           matrix_type=dbcsr_type_no_symmetry)
      END DO

      ! set up matrix S, and needed functions of S
      CALL ls_scf_init_matrix_s(matrix_s(1)%matrix, ls_scf_env)

      ! get the initial guess for the SCF
      CALL ls_scf_initial_guess(qs_env, ls_scf_env, nonscf)

      IF (ls_scf_env%do_rho_mixing) THEN
         CALL rho_mixing_ls_init(qs_env, ls_scf_env)
      END IF

      IF (ls_scf_env%do_pexsi) THEN
         CALL pexsi_init_scf(ks_env, ls_scf_env%pexsi, matrix_s(1)%matrix)
      END IF

      IF (qs_env%do_transport) THEN
         CALL transport_initialize(ks_env, qs_env%transport_env, matrix_s(1)%matrix)
      END IF

      CALL timestop(handle)

   END SUBROUTINE ls_scf_init_scf

! **************************************************************************************************
!> \brief deal with the scf initial guess
!> \param qs_env ...
!> \param ls_scf_env ...
!> \param nonscf ...
!> \par History
!>       2012.11 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE ls_scf_initial_guess(qs_env, ls_scf_env, nonscf)
      TYPE(qs_environment_type), POINTER                 :: qs_env
      TYPE(ls_scf_env_type)                              :: ls_scf_env
      LOGICAL, INTENT(IN)                                :: nonscf

      CHARACTER(len=*), PARAMETER :: routineN = 'ls_scf_initial_guess'
      INTEGER, PARAMETER                                 :: aspc_guess = 2, atomic_guess = 1, &
                                                            restart_guess = 3

      CHARACTER(LEN=default_path_length)                 :: file_name, project_name
      INTEGER                                            :: handle, iaspc, initial_guess_type, &
                                                            ispin, istore, naspc, unit_nr
      REAL(KIND=dp)                                      :: alpha, cs_pos
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_distribution_type)                      :: dist
      TYPE(dbcsr_type)                                   :: matrix_tmp1

      IF (ls_scf_env%do_pao) RETURN ! pao has its own initial guess

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      IF (unit_nr > 0) WRITE (unit_nr, '()')
      ! if there is no history go for the atomic guess, otherwise extrapolate the dm history
      IF (ls_scf_env%scf_history%istore == 0) THEN
         IF (ls_scf_env%restart_read) THEN
            initial_guess_type = restart_guess
         ELSE
            initial_guess_type = atomic_guess
         END IF
      ELSE
         initial_guess_type = aspc_guess
      END IF

      ! how to get the initial guess
      SELECT CASE (initial_guess_type)
      CASE (atomic_guess)
         CALL ls_scf_qs_atomic_guess(qs_env, ls_scf_env, ls_scf_env%energy_init, nonscf)
         IF (unit_nr > 0) WRITE (unit_nr, '()')
      CASE (restart_guess)
         project_name = logger%iter_info%project_name
         DO ispin = 1, SIZE(ls_scf_env%matrix_p)
            WRITE (file_name, '(A,I0,A)') TRIM(project_name)//"_LS_DM_SPIN_", ispin, "_RESTART.dm"
            CALL dbcsr_get_info(ls_scf_env%matrix_p(1), distribution=dist)
            CALL dbcsr_binary_read(file_name, distribution=dist, matrix_new=ls_scf_env%matrix_p(ispin))
            cs_pos = dbcsr_checksum(ls_scf_env%matrix_p(ispin), pos=.TRUE.)
            IF (unit_nr > 0) THEN
               WRITE (unit_nr, '(T2,A,E20.8)') "Read restart DM "//TRIM(file_name)//" with checksum: ", cs_pos
            END IF
         END DO

         ! directly go to computing the corresponding energy and ks matrix
         IF (nonscf) THEN
            CALL ls_nonscf_ks(qs_env, ls_scf_env, ls_scf_env%energy_init)
         ELSE
            CALL ls_scf_dm_to_ks(qs_env, ls_scf_env, ls_scf_env%energy_init, iscf=0)
         END IF
      CASE (aspc_guess)
         CALL cite_reference(Kolafa2004)
         CALL cite_reference(Kuhne2007)
         naspc = MIN(ls_scf_env%scf_history%istore, ls_scf_env%scf_history%nstore)
         DO ispin = 1, SIZE(ls_scf_env%matrix_p)
            ! actual extrapolation
            CALL dbcsr_set(ls_scf_env%matrix_p(ispin), 0.0_dp)
            DO iaspc = 1, naspc
               alpha = (-1.0_dp)**(iaspc + 1)*REAL(iaspc, KIND=dp)* &
                       binomial(2*naspc, naspc - iaspc)/binomial(2*naspc - 2, naspc - 1)
               istore = MOD(ls_scf_env%scf_history%istore - iaspc, ls_scf_env%scf_history%nstore) + 1
               CALL dbcsr_add(ls_scf_env%matrix_p(ispin), ls_scf_env%scf_history%matrix(ispin, istore), 1.0_dp, alpha)
            END DO
         END DO
      END SELECT

      ! which cases need getting purified and non-orthogonal ?
      SELECT CASE (initial_guess_type)
      CASE (atomic_guess, restart_guess)
         ! do nothing
      CASE (aspc_guess)
         ! purification can't be done on the pexsi matrix, which is not necessarily idempotent,
         ! and not stored in an ortho basis form
         IF (.NOT. (ls_scf_env%do_pexsi)) THEN
            DO ispin = 1, SIZE(ls_scf_env%matrix_p)
               ! linear combination of P's is not idempotent. A bit of McWeeny is needed to ensure it is again
               IF (SIZE(ls_scf_env%matrix_p) == 1) CALL dbcsr_scale(ls_scf_env%matrix_p(ispin), 0.5_dp)
               ! to ensure that noisy blocks do not build up during MD (in particular with curvy) filter that guess a bit more
               CALL dbcsr_filter(ls_scf_env%matrix_p(ispin), ls_scf_env%eps_filter**(2.0_dp/3.0_dp))
               CALL purify_mcweeny(ls_scf_env%matrix_p(ispin:ispin), ls_scf_env%eps_filter, 3)
               IF (SIZE(ls_scf_env%matrix_p) == 1) CALL dbcsr_scale(ls_scf_env%matrix_p(ispin), 2.0_dp)

               IF (ls_scf_env%use_s_sqrt) THEN
                  ! need to get P in the non-orthogonal basis if it was stored differently
                  CALL dbcsr_create(matrix_tmp1, template=ls_scf_env%matrix_s, &
                                    matrix_type=dbcsr_type_no_symmetry)
                  CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_sqrt_inv, ls_scf_env%matrix_p(ispin), &
                                      0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)
                  CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp1, ls_scf_env%matrix_s_sqrt_inv, &
                                      0.0_dp, ls_scf_env%matrix_p(ispin), &
                                      filter_eps=ls_scf_env%eps_filter)
                  CALL dbcsr_release(matrix_tmp1)

                  IF (ls_scf_env%has_s_preconditioner) THEN
                     CALL apply_matrix_preconditioner(ls_scf_env%matrix_p(ispin), "forward", &
                                                      ls_scf_env%matrix_bs_sqrt, ls_scf_env%matrix_bs_sqrt_inv)
                  END IF
               END IF
            END DO
         END IF

         ! compute corresponding energy and ks matrix
         IF (nonscf) THEN
            CALL ls_nonscf_ks(qs_env, ls_scf_env, ls_scf_env%energy_init)
         ELSE
            CALL ls_scf_dm_to_ks(qs_env, ls_scf_env, ls_scf_env%energy_init, iscf=0)
         END IF
      END SELECT

      IF (unit_nr > 0) THEN
         WRITE (unit_nr, '(T2,A,F20.9)') "Energy with the initial guess:", ls_scf_env%energy_init
         WRITE (unit_nr, '()')
      END IF

      CALL timestop(handle)

   END SUBROUTINE ls_scf_initial_guess

! **************************************************************************************************
!> \brief store a history of matrices for later use in ls_scf_initial_guess
!> \param ls_scf_env ...
!> \par History
!>       2012.11 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE ls_scf_store_result(ls_scf_env)
      TYPE(ls_scf_env_type)                              :: ls_scf_env

      CHARACTER(len=*), PARAMETER :: routineN = 'ls_scf_store_result'

      CHARACTER(LEN=default_path_length)                 :: file_name, project_name
      INTEGER                                            :: handle, ispin, istore, unit_nr
      REAL(KIND=dp)                                      :: cs_pos
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_type)                                   :: matrix_tmp1

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      IF (ls_scf_env%restart_write) THEN
         DO ispin = 1, SIZE(ls_scf_env%matrix_p)
            project_name = logger%iter_info%project_name
            WRITE (file_name, '(A,I0,A)') TRIM(project_name)//"_LS_DM_SPIN_", ispin, "_RESTART.dm"
            cs_pos = dbcsr_checksum(ls_scf_env%matrix_p(ispin), pos=.TRUE.)
            IF (unit_nr > 0) THEN
               WRITE (unit_nr, '(T2,A,E20.8)') "Writing restart DM "//TRIM(file_name)//" with checksum: ", cs_pos
            END IF
            IF (ls_scf_env%do_transport .OR. ls_scf_env%do_pexsi) THEN
               IF (unit_nr > 0) THEN
                  WRITE (unit_nr, '(T6,A)') "The restart DM "//TRIM(file_name)//" has the sparsity of S, therefore,"
                  WRITE (unit_nr, '(T6,A)') "not compatible with methods that require a full DM! "
               END IF
            END IF
            CALL dbcsr_binary_write(ls_scf_env%matrix_p(ispin), file_name)
         END DO
      END IF

      IF (ls_scf_env%scf_history%nstore > 0) THEN
         ls_scf_env%scf_history%istore = ls_scf_env%scf_history%istore + 1
         DO ispin = 1, SIZE(ls_scf_env%matrix_p)
            istore = MOD(ls_scf_env%scf_history%istore - 1, ls_scf_env%scf_history%nstore) + 1
            CALL dbcsr_copy(ls_scf_env%scf_history%matrix(ispin, istore), ls_scf_env%matrix_p(ispin))

            ! if we have the sqrt around, we use it to go to the orthogonal basis
            IF (ls_scf_env%use_s_sqrt) THEN
               ! usually sqrt(S) * P * sqrt(S) should be available, or could be stored at least,
               ! so that the next multiplications could be saved.
               CALL dbcsr_create(matrix_tmp1, template=ls_scf_env%matrix_s, &
                                 matrix_type=dbcsr_type_no_symmetry)

               IF (ls_scf_env%has_s_preconditioner) THEN
                  CALL apply_matrix_preconditioner(ls_scf_env%scf_history%matrix(ispin, istore), "backward", &
                                                   ls_scf_env%matrix_bs_sqrt, ls_scf_env%matrix_bs_sqrt_inv)
               END IF
               CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_sqrt, ls_scf_env%scf_history%matrix(ispin, istore), &
                                   0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)
               CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp1, ls_scf_env%matrix_s_sqrt, &
                                   0.0_dp, ls_scf_env%scf_history%matrix(ispin, istore), &
                                   filter_eps=ls_scf_env%eps_filter)
               CALL dbcsr_release(matrix_tmp1)
            END IF

         END DO
      END IF

      CALL timestop(handle)

   END SUBROUTINE ls_scf_store_result

! **************************************************************************************************
!> \brief Main SCF routine. Can we keep it clean ?
!> \param qs_env ...
!> \param ls_scf_env ...
!> \param nonscf ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE ls_scf_main(qs_env, ls_scf_env, nonscf)
      TYPE(qs_environment_type), POINTER                 :: qs_env
      TYPE(ls_scf_env_type)                              :: ls_scf_env
      LOGICAL, INTENT(IN), OPTIONAL                      :: nonscf

      CHARACTER(len=*), PARAMETER                        :: routineN = 'ls_scf_main'

      INTEGER                                            :: handle, iscf, ispin, &
                                                            nelectron_spin_real, nmixing, nspin, &
                                                            unit_nr
      LOGICAL :: check_convergence, diis_step, do_transport, extra_scf, maxscf_reached, &
         scf_converged, should_stop, transm_maxscf_reached, transm_scf_converged
      REAL(KIND=dp)                                      :: energy_diff, energy_new, energy_old, &
                                                            eps_diis, t1, t2, tdiag
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: matrix_ks, matrix_s
      TYPE(dbcsr_type), ALLOCATABLE, DIMENSION(:)        :: matrix_ks_deviation, matrix_mixing_old
      TYPE(energy_correction_type), POINTER              :: ec_env
      TYPE(qs_diis_buffer_type_sparse), POINTER          :: diis_buffer
      TYPE(transport_env_type), POINTER                  :: transport_env

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      nspin = ls_scf_env%nspins

      ! old quantities, useful for mixing
      ALLOCATE (matrix_mixing_old(nspin), matrix_ks_deviation(nspin))
      DO ispin = 1, nspin
         CALL dbcsr_create(matrix_mixing_old(ispin), template=ls_scf_env%matrix_ks(ispin))

         CALL dbcsr_create(matrix_ks_deviation(ispin), template=ls_scf_env%matrix_ks(ispin))
         CALL dbcsr_set(matrix_ks_deviation(ispin), 0.0_dp)
      END DO
      ls_scf_env%homo_spin(:) = 0.0_dp
      ls_scf_env%lumo_spin(:) = 0.0_dp

      transm_scf_converged = .FALSE.
      transm_maxscf_reached = .FALSE.

      energy_old = 0.0_dp
      IF (ls_scf_env%scf_history%istore > 0) energy_old = ls_scf_env%energy_init
      check_convergence = .TRUE.
      iscf = 0
      IF (ls_scf_env%ls_diis) THEN
         diis_step = .FALSE.
         eps_diis = ls_scf_env%eps_diis
         nmixing = ls_scf_env%nmixing
         NULLIFY (diis_buffer)
         ALLOCATE (diis_buffer)
         CALL qs_diis_b_create_sparse(diis_buffer, &
                                      nbuffer=ls_scf_env%max_diis)
         CALL qs_diis_b_clear_sparse(diis_buffer)
         CALL get_qs_env(qs_env, matrix_s=matrix_s)
      END IF

      CALL get_qs_env(qs_env, transport_env=transport_env, do_transport=do_transport)

      ! the real SCF loop
      DO

         ! check on max SCF or timing/exit
         CALL external_control(should_stop, "SCF", start_time=qs_env%start_time, target_time=qs_env%target_time)
         IF (do_transport) THEN
            maxscf_reached = should_stop .OR. iscf >= ls_scf_env%max_scf
            ! one extra scf step for post-processing in transmission calculations
            IF (transport_env%params%method == transport_transmission) THEN
               IF (transm_maxscf_reached) THEN
                  IF (unit_nr > 0) WRITE (unit_nr, '(T2,A)') "SCF not converged! "
                  EXIT
               END IF
               transm_maxscf_reached = maxscf_reached
            ELSE
               IF (maxscf_reached) THEN
                  IF (unit_nr > 0) WRITE (unit_nr, '(T2,A)') "SCF not converged! "
                  EXIT
               END IF
            END IF
         ELSE
            IF (should_stop .OR. iscf >= ls_scf_env%max_scf) THEN
               IF (unit_nr > 0) WRITE (unit_nr, '(T2,A)') "SCF not converged! "
               ! Skip Harris functional calculation if ground-state is NOT converged
               IF (qs_env%energy_correction) THEN
                  CALL get_qs_env(qs_env, ec_env=ec_env)
                  IF (ec_env%skip_ec) ec_env%do_skip = .TRUE.
               END IF
               EXIT
            END IF
         END IF

         t1 = m_walltime()
         iscf = iscf + 1

         ! first get a copy of the current KS matrix
         CALL get_qs_env(qs_env, matrix_ks=matrix_ks)
         DO ispin = 1, nspin
            CALL matrix_qs_to_ls(ls_scf_env%matrix_ks(ispin), matrix_ks(ispin)%matrix, &
                                 ls_scf_env%ls_mstruct, covariant=.TRUE.)
            IF (ls_scf_env%has_s_preconditioner) THEN
               CALL apply_matrix_preconditioner(ls_scf_env%matrix_ks(ispin), "forward", &
                                                ls_scf_env%matrix_bs_sqrt, ls_scf_env%matrix_bs_sqrt_inv)
            END IF
            CALL dbcsr_filter(ls_scf_env%matrix_ks(ispin), ls_scf_env%eps_filter)
         END DO
         ! run curvy steps if required. Needs an idempotent DM (either perification or restart)
         IF ((iscf > 1 .OR. ls_scf_env%scf_history%istore > 0) .AND. ls_scf_env%curvy_steps) THEN
            CALL dm_ls_curvy_optimization(ls_scf_env, energy_old, check_convergence)
         ELSE
            ! turn the KS matrix in a density matrix
            DO ispin = 1, nspin
               IF (nonscf) THEN
                  CALL dbcsr_copy(matrix_mixing_old(ispin), ls_scf_env%matrix_ks(ispin))
               ELSEIF (ls_scf_env%do_rho_mixing) THEN
                  CALL dbcsr_copy(matrix_mixing_old(ispin), ls_scf_env%matrix_ks(ispin))
               ELSE
                  IF (iscf == 1) THEN
                     ! initialize the mixing matrix with the current state if needed
                     CALL dbcsr_copy(matrix_mixing_old(ispin), ls_scf_env%matrix_ks(ispin))
                  ELSE
                     IF (ls_scf_env%ls_diis) THEN ! ------- IF-DIIS+MIX--- START
                        IF (diis_step .AND. (iscf - 1) >= ls_scf_env%iter_ini_diis) THEN
                           IF (unit_nr > 0) THEN
                              WRITE (unit_nr, '(A61)') &
                                 '*************************************************************'
                              WRITE (unit_nr, '(A50,2(I3,A1),L1,A1)') &
                                 " Using DIIS mixed KS:  (iscf,INI_DIIS,DIIS_STEP)=(", &
                                 iscf, ",", ls_scf_env%iter_ini_diis, ",", diis_step, ")"
                              WRITE (unit_nr, '(A52)') &
                                 " KS_nw= DIIS-Linear-Combination-Previous KS matrices"
                              WRITE (unit_nr, '(61A)') &
                                 "*************************************************************"
                           END IF
                           CALL dbcsr_copy(matrix_mixing_old(ispin), & ! out
                                           ls_scf_env%matrix_ks(ispin)) ! in
                        ELSE
                           IF (unit_nr > 0) THEN
                              WRITE (unit_nr, '(A57)') &
                                 "*********************************************************"
                              WRITE (unit_nr, '(A23,F5.3,A25,I3)') &
                                 " Using MIXING_FRACTION=", ls_scf_env%mixing_fraction, &
                                 " to mix KS matrix:  iscf=", iscf
                              WRITE (unit_nr, '(A7,F5.3,A6,F5.3,A7)') &
                                 " KS_nw=", ls_scf_env%mixing_fraction, "*KS + ", &
                                 1.0_dp - ls_scf_env%mixing_fraction, "*KS_old"
                              WRITE (unit_nr, '(A57)') &
                                 "*********************************************************"
                           END IF
                           ! perform the mixing of ks matrices
                           CALL dbcsr_add(matrix_mixing_old(ispin), &
                                          ls_scf_env%matrix_ks(ispin), &
                                          1.0_dp - ls_scf_env%mixing_fraction, &
                                          ls_scf_env%mixing_fraction)
                        END IF
                     ELSE ! otherwise
                        IF (unit_nr > 0) THEN
                           WRITE (unit_nr, '(A57)') &
                              "*********************************************************"
                           WRITE (unit_nr, '(A23,F5.3,A25,I3)') &
                              " Using MIXING_FRACTION=", ls_scf_env%mixing_fraction, &
                              " to mix KS matrix:  iscf=", iscf
                           WRITE (unit_nr, '(A7,F5.3,A6,F5.3,A7)') &
                              " KS_nw=", ls_scf_env%mixing_fraction, "*KS + ", &
                              1.0_dp - ls_scf_env%mixing_fraction, "*KS_old"
                           WRITE (unit_nr, '(A57)') &
                              "*********************************************************"
                        END IF
                        ! perform the mixing of ks matrices
                        CALL dbcsr_add(matrix_mixing_old(ispin), &
                                       ls_scf_env%matrix_ks(ispin), &
                                       1.0_dp - ls_scf_env%mixing_fraction, &
                                       ls_scf_env%mixing_fraction)
                     END IF ! ------- IF-DIIS+MIX--- END
                  END IF
               END IF

               ! compute the density matrix that matches it
               ! we need the proper number of states
               nelectron_spin_real = ls_scf_env%nelectron_spin(ispin)
               IF (ls_scf_env%nspins == 1) nelectron_spin_real = nelectron_spin_real/2

               IF (do_transport) THEN
                  IF (ls_scf_env%has_s_preconditioner) &
                     CPABORT("NOT YET IMPLEMENTED with S preconditioner. ")
                  IF (ls_scf_env%ls_mstruct%cluster_type /= ls_cluster_atomic) &
                     CPABORT("NOT YET IMPLEMENTED with molecular clustering. ")

                  extra_scf = maxscf_reached .OR. scf_converged
                  ! get the current Kohn-Sham matrix (ks) and return matrix_p evaluated using an external C routine
                  CALL external_scf_method(transport_env, ls_scf_env%matrix_s, matrix_mixing_old(ispin), &
                                           ls_scf_env%matrix_p(ispin), nelectron_spin_real, ls_scf_env%natoms, &
                                           energy_diff, iscf, extra_scf)

               ELSE
                  SELECT CASE (ls_scf_env%purification_method)
                  CASE (ls_scf_sign)
                     CALL density_matrix_sign(ls_scf_env%matrix_p(ispin), ls_scf_env%mu_spin(ispin), ls_scf_env%fixed_mu, &
                                              ls_scf_env%sign_method, ls_scf_env%sign_order, matrix_mixing_old(ispin), &
                                              ls_scf_env%matrix_s, ls_scf_env%matrix_s_inv, nelectron_spin_real, &
                                              ls_scf_env%eps_filter, ls_scf_env%sign_symmetric, ls_scf_env%submatrix_sign_method, &
                                              ls_scf_env%matrix_s_sqrt_inv)
                  CASE (ls_scf_tc2)
                     CALL density_matrix_tc2(ls_scf_env%matrix_p(ispin), matrix_mixing_old(ispin), ls_scf_env%matrix_s_sqrt_inv, &
                                             nelectron_spin_real, ls_scf_env%eps_filter, ls_scf_env%homo_spin(ispin), &
                                             ls_scf_env%lumo_spin(ispin), non_monotonic=ls_scf_env%non_monotonic, &
                                             eps_lanczos=ls_scf_env%eps_lanczos, max_iter_lanczos=ls_scf_env%max_iter_lanczos, &
                                             iounit=-1)
                  CASE (ls_scf_trs4)
                     CALL density_matrix_trs4(ls_scf_env%matrix_p(ispin), matrix_mixing_old(ispin), ls_scf_env%matrix_s_sqrt_inv, &
                                              nelectron_spin_real, ls_scf_env%eps_filter, ls_scf_env%homo_spin(ispin), &
                                              ls_scf_env%lumo_spin(ispin), ls_scf_env%mu_spin(ispin), &
                                              dynamic_threshold=ls_scf_env%dynamic_threshold, &
                                              matrix_ks_deviation=matrix_ks_deviation(ispin), &
                                              eps_lanczos=ls_scf_env%eps_lanczos, max_iter_lanczos=ls_scf_env%max_iter_lanczos, &
                                              iounit=-1)
                  CASE (ls_scf_pexsi)
                     IF (ls_scf_env%has_s_preconditioner) &
                        CPABORT("S preconditioning not implemented in combination with the PEXSI library. ")
                     IF (ls_scf_env%ls_mstruct%cluster_type /= ls_cluster_atomic) &
                        CALL cp_abort(__LOCATION__, &
                                      "Molecular clustering not implemented in combination with the PEXSI library. ")
                     CALL density_matrix_pexsi(ls_scf_env%pexsi, ls_scf_env%matrix_p(ispin), ls_scf_env%pexsi%matrix_w(ispin), &
                                               ls_scf_env%pexsi%kTS(ispin), matrix_mixing_old(ispin), ls_scf_env%matrix_s, &
                                               nelectron_spin_real, ls_scf_env%mu_spin(ispin), iscf, ispin)
                  END SELECT
               END IF

               IF (ls_scf_env%has_s_preconditioner) THEN
                  CALL apply_matrix_preconditioner(ls_scf_env%matrix_p(ispin), "forward", &
                                                   ls_scf_env%matrix_bs_sqrt, ls_scf_env%matrix_bs_sqrt_inv)
               END IF
               CALL dbcsr_filter(ls_scf_env%matrix_p(ispin), ls_scf_env%eps_filter)

               IF (ls_scf_env%nspins == 1) CALL dbcsr_scale(ls_scf_env%matrix_p(ispin), 2.0_dp)

            END DO
         END IF

         ! compute the corresponding new energy KS matrix and new energy
         IF (nonscf) THEN
            CALL ls_nonscf_energy(qs_env, ls_scf_env)
         ELSE
            CALL ls_scf_dm_to_ks(qs_env, ls_scf_env, energy_new, iscf)
         END IF

         IF (ls_scf_env%do_pexsi) THEN
            CALL pexsi_to_qs(ls_scf_env, qs_env, kTS=ls_scf_env%pexsi%kTS)
         END IF

         t2 = m_walltime()
         IF (nonscf) THEN
            tdiag = t2 - t1
            CALL qs_nonscf_print_summary(qs_env, tdiag, ls_scf_env%nelectron_total, unit_nr)
            EXIT
         ELSE
            ! report current SCF loop
            energy_diff = energy_new - energy_old
            energy_old = energy_new
            IF (unit_nr > 0) THEN
               WRITE (unit_nr, *)
               WRITE (unit_nr, '(T2,A,I6,F20.9,F20.9,F12.6)') "SCF", iscf, energy_new, energy_diff, t2 - t1
               WRITE (unit_nr, *)
               CALL m_flush(unit_nr)
            END IF
         END IF

         IF (do_transport) THEN
            scf_converged = check_convergence .AND. ABS(energy_diff) < ls_scf_env%eps_scf*ls_scf_env%nelectron_total
            ! one extra scf step for post-processing in transmission calculations
            IF (transport_env%params%method == transport_transmission) THEN
               IF (transm_scf_converged) EXIT
               transm_scf_converged = scf_converged
            ELSE
               IF (scf_converged) THEN
                  IF (unit_nr > 0) WRITE (unit_nr, '(/,T2,A,I5,A/)') "SCF run converged in ", iscf, " steps."
                  EXIT
               END IF
            END IF
         ELSE
            ! exit criterion on the energy only for the time being
            IF (check_convergence .AND. ABS(energy_diff) < ls_scf_env%eps_scf*ls_scf_env%nelectron_total) THEN
               IF (unit_nr > 0) WRITE (unit_nr, '(/,T2,A,I5,A/)') "SCF run converged in ", iscf, " steps."
               ! Skip Harris functional calculation if ground-state is NOT converged
               IF (qs_env%energy_correction) THEN
                  CALL get_qs_env(qs_env, ec_env=ec_env)
                  IF (ec_env%skip_ec) ec_env%do_skip = .FALSE.
               END IF
               EXIT
            END IF
         END IF

         IF (ls_scf_env%ls_diis) THEN
! diis_buffer, buffer with 1) Kohn-Sham history matrix,
!                          2) KS error history matrix (f=KPS-SPK),
!                          3) B matrix (for finding DIIS weighting coefficients)
            CALL qs_diis_b_step_4lscf(diis_buffer, qs_env, ls_scf_env, unit_nr, &
                                      iscf, diis_step, eps_diis, nmixing, matrix_s(1)%matrix, &
                                      ls_scf_env%eps_filter)
         END IF

         IF (ls_scf_env%do_pexsi) THEN
            CALL pexsi_set_convergence_tolerance(ls_scf_env%pexsi, energy_diff, &
                                                 ls_scf_env%eps_scf*ls_scf_env%nelectron_total, &
                                                 ! initialize in second scf step of first SCF cycle:
                                                 (iscf == 2) .AND. (ls_scf_env%scf_history%istore == 0), &
                                                 check_convergence)
         END IF

      END DO

      ! free storage
      IF (ls_scf_env%ls_diis) THEN
         CALL qs_diis_b_release_sparse(diis_buffer)
         DEALLOCATE (diis_buffer)
      END IF
      DO ispin = 1, nspin
         CALL dbcsr_release(matrix_mixing_old(ispin))
         CALL dbcsr_release(matrix_ks_deviation(ispin))
      END DO
      DEALLOCATE (matrix_mixing_old, matrix_ks_deviation)

      CALL timestop(handle)

   END SUBROUTINE ls_scf_main

! **************************************************************************************************
!> \brief after SCF we have a density matrix, and the self consistent KS matrix
!>        analyze its properties.
!> \param qs_env ...
!> \param ls_scf_env ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE ls_scf_post(qs_env, ls_scf_env)
      TYPE(qs_environment_type), POINTER                 :: qs_env
      TYPE(ls_scf_env_type)                              :: ls_scf_env

      CHARACTER(len=*), PARAMETER                        :: routineN = 'ls_scf_post'

      INTEGER                                            :: handle, ispin, unit_nr
      REAL(KIND=dp)                                      :: occ
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: matrix_w
      TYPE(dft_control_type), POINTER                    :: dft_control

      CALL timeset(routineN, handle)

      CALL get_qs_env(qs_env, dft_control=dft_control)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      ! store the matrix for a next scf run
      IF (.NOT. ls_scf_env%do_pao) &
         CALL ls_scf_store_result(ls_scf_env)

      ! write homo and lumo energy and occupation (if not already part of the output)
      IF (ls_scf_env%curvy_steps) THEN
         CALL post_scf_homo_lumo(ls_scf_env)

         ! always report P occ
         IF (unit_nr > 0) WRITE (unit_nr, *) ""
         DO ispin = 1, ls_scf_env%nspins
            occ = dbcsr_get_occupation(ls_scf_env%matrix_p(ispin))
            IF (unit_nr > 0) WRITE (unit_nr, '(T2,A,F20.12)') "Density matrix (P) occupation ", occ
         END DO
      END IF

      ! compute the matrix_w if associated
      IF (ls_scf_env%calculate_forces) THEN
         CALL get_qs_env(qs_env, matrix_w=matrix_w)
         CPASSERT(ASSOCIATED(matrix_w))
         IF (ls_scf_env%do_pexsi) THEN
            CALL pexsi_to_qs(ls_scf_env, qs_env, matrix_w=ls_scf_env%pexsi%matrix_w)
         ELSE
            CALL calculate_w_matrix_ls(matrix_w, ls_scf_env)
         END IF
      END IF

      ! compute properties

      IF (ls_scf_env%perform_mu_scan) CALL post_scf_mu_scan(ls_scf_env)

      IF (ls_scf_env%report_all_sparsities) CALL post_scf_sparsities(ls_scf_env)

      IF (dft_control%qs_control%dftb) THEN
         CALL scf_post_calculation_tb(qs_env, "DFTB", .TRUE.)
      ELSE IF (dft_control%qs_control%xtb) THEN
         CALL scf_post_calculation_tb(qs_env, "xTB", .TRUE.)
      ELSE
         CALL write_mo_free_results(qs_env)
      END IF

      IF (ls_scf_env%chebyshev%compute_chebyshev) CALL compute_chebyshev(qs_env, ls_scf_env)

      IF (.TRUE.) CALL post_scf_experiment()

      IF (dft_control%qs_control%dftb .OR. dft_control%qs_control%xtb) THEN
         !
      ELSE
         CALL qs_scf_post_moments(qs_env%input, logger, qs_env, unit_nr)
      END IF

      ! clean up used data

      CALL dbcsr_release(ls_scf_env%matrix_s)
      CALL deallocate_curvy_data(ls_scf_env%curvy_data)

      IF (ls_scf_env%has_s_preconditioner) THEN
         CALL dbcsr_release(ls_scf_env%matrix_bs_sqrt)
         CALL dbcsr_release(ls_scf_env%matrix_bs_sqrt_inv)
      END IF

      IF (ls_scf_env%needs_s_inv) THEN
         CALL dbcsr_release(ls_scf_env%matrix_s_inv)
      END IF

      IF (ls_scf_env%use_s_sqrt) THEN
         CALL dbcsr_release(ls_scf_env%matrix_s_sqrt)
         CALL dbcsr_release(ls_scf_env%matrix_s_sqrt_inv)
      END IF

      DO ispin = 1, SIZE(ls_scf_env%matrix_ks)
         CALL dbcsr_release(ls_scf_env%matrix_ks(ispin))
      END DO
      DEALLOCATE (ls_scf_env%matrix_ks)

      IF (ls_scf_env%do_pexsi) &
         CALL pexsi_finalize_scf(ls_scf_env%pexsi, ls_scf_env%mu_spin)

      CALL timestop(handle)

   END SUBROUTINE ls_scf_post

! **************************************************************************************************
!> \brief Compute the HOMO LUMO energies post SCF
!> \param ls_scf_env ...
!> \par History
!>       2013.06 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE post_scf_homo_lumo(ls_scf_env)
      TYPE(ls_scf_env_type)                              :: ls_scf_env

      CHARACTER(len=*), PARAMETER :: routineN = 'post_scf_homo_lumo'

      INTEGER                                            :: handle, ispin, nspin, unit_nr
      LOGICAL                                            :: converged
      REAL(KIND=dp)                                      :: eps_max, eps_min, homo, lumo
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_type)                                   :: matrix_k, matrix_p, matrix_tmp

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      IF (unit_nr > 0) WRITE (unit_nr, '(T2,A)') ""

      ! TODO: remove these limitations
      CPASSERT(.NOT. ls_scf_env%has_s_preconditioner)
      CPASSERT(ls_scf_env%use_s_sqrt)

      nspin = ls_scf_env%nspins

      CALL dbcsr_create(matrix_p, template=ls_scf_env%matrix_p(1), matrix_type=dbcsr_type_no_symmetry)

      CALL dbcsr_create(matrix_k, template=ls_scf_env%matrix_p(1), matrix_type=dbcsr_type_no_symmetry)

      CALL dbcsr_create(matrix_tmp, template=ls_scf_env%matrix_p(1), matrix_type=dbcsr_type_no_symmetry)

      DO ispin = 1, nspin
         ! ortho basis ks
         CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_sqrt_inv, ls_scf_env%matrix_ks(ispin), &
                             0.0_dp, matrix_tmp, filter_eps=ls_scf_env%eps_filter)
         CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp, ls_scf_env%matrix_s_sqrt_inv, &
                             0.0_dp, matrix_k, filter_eps=ls_scf_env%eps_filter)

         ! extremal eigenvalues ks
         CALL arnoldi_extremal(matrix_k, eps_max, eps_min, max_iter=ls_scf_env%max_iter_lanczos, &
                               threshold=ls_scf_env%eps_lanczos, converged=converged)

         ! ortho basis p
         CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_sqrt, ls_scf_env%matrix_p(ispin), &
                             0.0_dp, matrix_tmp, filter_eps=ls_scf_env%eps_filter)
         CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp, ls_scf_env%matrix_s_sqrt, &
                             0.0_dp, matrix_p, filter_eps=ls_scf_env%eps_filter)
         IF (nspin == 1) CALL dbcsr_scale(matrix_p, 0.5_dp)

         ! go compute homo lumo
         CALL compute_homo_lumo(matrix_k, matrix_p, eps_min, eps_max, ls_scf_env%eps_filter, &
                                ls_scf_env%max_iter_lanczos, ls_scf_env%eps_lanczos, homo, lumo, unit_nr)

      END DO

      CALL dbcsr_release(matrix_p)
      CALL dbcsr_release(matrix_k)
      CALL dbcsr_release(matrix_tmp)

      CALL timestop(handle)

   END SUBROUTINE post_scf_homo_lumo

! **************************************************************************************************
!> \brief Compute the density matrix for various values of the chemical potential
!> \param ls_scf_env ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE post_scf_mu_scan(ls_scf_env)
      TYPE(ls_scf_env_type)                              :: ls_scf_env

      CHARACTER(len=*), PARAMETER                        :: routineN = 'post_scf_mu_scan'

      INTEGER                                            :: handle, imu, ispin, nelectron_spin_real, &
                                                            nmu, nspin, unit_nr
      REAL(KIND=dp)                                      :: mu, t1, t2, trace
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_type)                                   :: matrix_p

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      nspin = ls_scf_env%nspins

      CALL dbcsr_create(matrix_p, template=ls_scf_env%matrix_p(1))

      nmu = 10
      DO imu = 0, nmu

         t1 = m_walltime()

         mu = -0.4_dp + imu*(0.1_dp + 0.4_dp)/nmu

         IF (unit_nr > 0) WRITE (unit_nr, *) "------- starting with mu ", mu

         DO ispin = 1, nspin
            ! we need the proper number of states
            nelectron_spin_real = ls_scf_env%nelectron_spin(ispin)
            IF (ls_scf_env%nspins == 1) nelectron_spin_real = nelectron_spin_real/2

            CALL density_matrix_sign_fixed_mu(matrix_p, trace, mu, ls_scf_env%sign_method, &
                                              ls_scf_env%sign_order, ls_scf_env%matrix_ks(ispin), &
                                              ls_scf_env%matrix_s, ls_scf_env%matrix_s_inv, &
                                              ls_scf_env%eps_filter, ls_scf_env%sign_symmetric, &
                                              ls_scf_env%submatrix_sign_method, ls_scf_env%matrix_s_sqrt_inv)
         END DO

         t2 = m_walltime()

         IF (unit_nr > 0) WRITE (unit_nr, *) " obtained ", mu, trace, t2 - t1

      END DO

      CALL dbcsr_release(matrix_p)

      CALL timestop(handle)

   END SUBROUTINE post_scf_mu_scan

! **************************************************************************************************
!> \brief Report on the sparsities of various interesting matrices.
!>
!> \param ls_scf_env ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE post_scf_sparsities(ls_scf_env)
      TYPE(ls_scf_env_type)                              :: ls_scf_env

      CHARACTER(len=*), PARAMETER :: routineN = 'post_scf_sparsities'

      CHARACTER(LEN=default_string_length)               :: title
      INTEGER                                            :: handle, ispin, nspin, unit_nr
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(dbcsr_type)                                   :: matrix_tmp1, matrix_tmp2

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      nspin = ls_scf_env%nspins

      IF (unit_nr > 0) THEN
         WRITE (unit_nr, '()')
         WRITE (unit_nr, '(T2,A,E17.3)') "Sparsity reports for eps_filter: ", ls_scf_env%eps_filter
         WRITE (unit_nr, '()')
      END IF

      CALL report_matrix_sparsity(ls_scf_env%matrix_s, unit_nr, "overlap matrix (S)", &
                                  ls_scf_env%eps_filter)

      DO ispin = 1, nspin
         WRITE (title, '(A,I3)') "Kohn-Sham matrix (H) for spin ", ispin
         CALL report_matrix_sparsity(ls_scf_env%matrix_ks(ispin), unit_nr, title, &
                                     ls_scf_env%eps_filter)
      END DO

      CALL dbcsr_create(matrix_tmp1, template=ls_scf_env%matrix_s, matrix_type=dbcsr_type_no_symmetry)
      CALL dbcsr_create(matrix_tmp2, template=ls_scf_env%matrix_s, matrix_type=dbcsr_type_no_symmetry)

      DO ispin = 1, nspin
         WRITE (title, '(A,I3)') "Density matrix (P) for spin ", ispin
         CALL report_matrix_sparsity(ls_scf_env%matrix_p(ispin), unit_nr, title, &
                                     ls_scf_env%eps_filter)

         CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s, ls_scf_env%matrix_p(ispin), &
                             0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)

         WRITE (title, '(A,I3,A)') "S * P(", ispin, ")"
         CALL report_matrix_sparsity(matrix_tmp1, unit_nr, title, ls_scf_env%eps_filter)

         CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp1, ls_scf_env%matrix_s, &
                             0.0_dp, matrix_tmp2, filter_eps=ls_scf_env%eps_filter)
         WRITE (title, '(A,I3,A)') "S * P(", ispin, ") * S"
         CALL report_matrix_sparsity(matrix_tmp2, unit_nr, title, ls_scf_env%eps_filter)
      END DO

      IF (ls_scf_env%needs_s_inv) THEN
         CALL report_matrix_sparsity(ls_scf_env%matrix_s_inv, unit_nr, "inv(S)", &
                                     ls_scf_env%eps_filter)
         DO ispin = 1, nspin
            CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_inv, ls_scf_env%matrix_ks(ispin), &
                                0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)

            WRITE (title, '(A,I3,A)') "inv(S) * H(", ispin, ")"
            CALL report_matrix_sparsity(matrix_tmp1, unit_nr, title, ls_scf_env%eps_filter)
         END DO
      END IF

      IF (ls_scf_env%use_s_sqrt) THEN

         CALL report_matrix_sparsity(ls_scf_env%matrix_s_sqrt, unit_nr, "sqrt(S)", &
                                     ls_scf_env%eps_filter)
         CALL report_matrix_sparsity(ls_scf_env%matrix_s_sqrt_inv, unit_nr, "inv(sqrt(S))", &
                                     ls_scf_env%eps_filter)

         DO ispin = 1, nspin
            CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_sqrt_inv, ls_scf_env%matrix_ks(ispin), &
                                0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)
            CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp1, ls_scf_env%matrix_s_sqrt_inv, &
                                0.0_dp, matrix_tmp2, filter_eps=ls_scf_env%eps_filter)
            WRITE (title, '(A,I3,A)') "inv(sqrt(S)) * H(", ispin, ") * inv(sqrt(S))"
            CALL report_matrix_sparsity(matrix_tmp2, unit_nr, title, ls_scf_env%eps_filter)
         END DO

         DO ispin = 1, nspin
            CALL dbcsr_multiply("N", "N", 1.0_dp, ls_scf_env%matrix_s_sqrt, ls_scf_env%matrix_p(ispin), &
                                0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)
            CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp1, ls_scf_env%matrix_s_sqrt, &
                                0.0_dp, matrix_tmp2, filter_eps=ls_scf_env%eps_filter)
            WRITE (title, '(A,I3,A)') "sqrt(S) * P(", ispin, ") * sqrt(S)"
            CALL report_matrix_sparsity(matrix_tmp2, unit_nr, title, ls_scf_env%eps_filter)
         END DO

      END IF

      CALL dbcsr_release(matrix_tmp1)
      CALL dbcsr_release(matrix_tmp2)

      CALL timestop(handle)

   END SUBROUTINE post_scf_sparsities

! **************************************************************************************************
!> \brief Helper routine to report on the sparsity of a single matrix,
!>        for several filtering values
!> \param matrix ...
!> \param unit_nr ...
!> \param title ...
!> \param eps ...
!> \par History
!>       2010.10 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE report_matrix_sparsity(matrix, unit_nr, title, eps)
      TYPE(dbcsr_type)                                   :: matrix
      INTEGER                                            :: unit_nr
      CHARACTER(LEN=*)                                   :: title
      REAL(KIND=dp)                                      :: eps

      CHARACTER(len=*), PARAMETER :: routineN = 'report_matrix_sparsity'

      INTEGER                                            :: handle
      REAL(KIND=dp)                                      :: eps_local, occ
      TYPE(dbcsr_type)                                   :: matrix_tmp

      CALL timeset(routineN, handle)
      CALL dbcsr_create(matrix_tmp, template=matrix, name=TRIM(title))
      CALL dbcsr_copy(matrix_tmp, matrix, name=TRIM(title))

      IF (unit_nr > 0) THEN
         WRITE (unit_nr, '(T2,A)') "Sparsity for : "//TRIM(title)
      END IF

      eps_local = MAX(eps, 10E-14_dp)
      DO
         IF (eps_local > 1.1_dp) EXIT
         CALL dbcsr_filter(matrix_tmp, eps_local)
         occ = dbcsr_get_occupation(matrix_tmp)
         IF (unit_nr > 0) WRITE (unit_nr, '(T2,F16.12,A3,F16.12)') eps_local, " : ", occ
         eps_local = eps_local*10
      END DO

      CALL dbcsr_release(matrix_tmp)

      CALL timestop(handle)

   END SUBROUTINE report_matrix_sparsity

! **************************************************************************************************
!> \brief Compute matrix_w as needed for the forces
!> \param matrix_w ...
!> \param ls_scf_env ...
!> \par History
!>       2010.11 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE calculate_w_matrix_ls(matrix_w, ls_scf_env)
      TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: matrix_w
      TYPE(ls_scf_env_type)                              :: ls_scf_env

      CHARACTER(len=*), PARAMETER :: routineN = 'calculate_w_matrix_ls'

      INTEGER                                            :: handle, ispin
      REAL(KIND=dp)                                      :: scaling
      TYPE(dbcsr_type)                                   :: matrix_tmp1, matrix_tmp2, matrix_tmp3

      CALL timeset(routineN, handle)

      CALL dbcsr_create(matrix_tmp1, template=ls_scf_env%matrix_s, matrix_type=dbcsr_type_no_symmetry)
      CALL dbcsr_create(matrix_tmp2, template=ls_scf_env%matrix_s, matrix_type=dbcsr_type_no_symmetry)
      CALL dbcsr_create(matrix_tmp3, template=ls_scf_env%matrix_s, matrix_type=dbcsr_type_no_symmetry)

      IF (ls_scf_env%nspins == 1) THEN
         scaling = 0.5_dp
      ELSE
         scaling = 1.0_dp
      END IF

      DO ispin = 1, ls_scf_env%nspins

         CALL dbcsr_copy(matrix_tmp3, ls_scf_env%matrix_ks(ispin))
         IF (ls_scf_env%has_s_preconditioner) THEN
            CALL apply_matrix_preconditioner(matrix_tmp3, "backward", &
                                             ls_scf_env%matrix_bs_sqrt, ls_scf_env%matrix_bs_sqrt_inv)
         END IF
         CALL dbcsr_filter(matrix_tmp3, ls_scf_env%eps_filter)

         CALL dbcsr_multiply("N", "N", scaling, ls_scf_env%matrix_p(ispin), matrix_tmp3, &
                             0.0_dp, matrix_tmp1, filter_eps=ls_scf_env%eps_filter)
         CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_tmp1, ls_scf_env%matrix_p(ispin), &
                             0.0_dp, matrix_tmp2, filter_eps=ls_scf_env%eps_filter)
         CALL matrix_ls_to_qs(matrix_w(ispin)%matrix, matrix_tmp2, ls_scf_env%ls_mstruct, covariant=.FALSE.)
      END DO

      CALL dbcsr_release(matrix_tmp1)
      CALL dbcsr_release(matrix_tmp2)
      CALL dbcsr_release(matrix_tmp3)

      CALL timestop(handle)

   END SUBROUTINE calculate_w_matrix_ls

! **************************************************************************************************
!> \brief a place for quick experiments
!> \par History
!>       2010.11 created [Joost VandeVondele]
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE post_scf_experiment()

      CHARACTER(len=*), PARAMETER :: routineN = 'post_scf_experiment'

      INTEGER                                            :: handle, unit_nr
      TYPE(cp_logger_type), POINTER                      :: logger

      CALL timeset(routineN, handle)

      ! get a useful output_unit
      logger => cp_get_default_logger()
      IF (logger%para_env%is_source()) THEN
         unit_nr = cp_logger_get_default_unit_nr(logger, local=.TRUE.)
      ELSE
         unit_nr = -1
      END IF

      CALL timestop(handle)
   END SUBROUTINE post_scf_experiment

END MODULE dm_ls_scf
